#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Text;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// Formats and prints Yarr objects
	/// </summary>
	public static class Printer
	{
        const int MAX_ARRAY_PRINT_LEN = 20;
        const int MAX_CONS_PRINT_LEN = 20;

        public static string WriteToString (Object x)
        {
            return WriteToString(x, true);
        }

		public static string WriteToString(Object x, bool encloseList) 
		{
			if (x == null)
			{
				return "null";
			}

            if (x is CastInstance)
                x = ((CastInstance)x).Value;

            if (x is string) 
			{
				return string.Format(CultureInfo.CurrentCulture, "\"{0}\"", x);
			}

			if (x is char) 
			{
                return string.Format(CultureInfo.CurrentCulture, "#\\{0}", x);
			}

			if (x is Symbol) 
			{
                return string.Format(CultureInfo.CurrentCulture, "{0}", ((Symbol)x).PrintName);
			}

            if (x is Array)
            {
                Array arr = (Array)x;
                StringBuilder sb = new StringBuilder();

                // For large arrays, don't try to print the elements
                if (arr.Length > MAX_ARRAY_PRINT_LEN)
                {
                    sb.Append("#<Array[");
                    for (int ix = 0; ix < arr.Rank; ++ix)
                    {
                        if (ix > 0)
                            sb.Append(" x ");
                        sb.Append(arr.GetUpperBound(ix) - arr.GetLowerBound(ix) + 1);
                    }
                    sb.Append("] ");
                }

                int[] ind = new int[arr.Rank];
                int[] lb = new int[arr.Rank];
                int[] ub = new int[arr.Rank];

                if (arr.Length <= MAX_ARRAY_PRINT_LEN)
                    sb.Append(String.Format(CultureInfo.CurrentCulture, 
                                            arr.Rank > 1 ? "#{0}a" : "#", arr.Rank));

                for (int ix = 0; ix < arr.Rank; ++ix)
                {
                    ind[ix] = lb[ix] = arr.GetLowerBound(ix);
                    ub[ix] = arr.GetUpperBound(ix);
                    sb.Append("(");
                }

                int printedElts = 0;
                do
                {
                    try
                    {
                        sb.Append(WriteToString(arr.GetValue(ind)));
                        ++printedElts;
                    }
                    catch (IndexOutOfRangeException)
                    {
                        // ignore - we just don't print out 0-length arrays
                    }
                }
                while (IncrementIndex(arr.Rank - 1, ind, lb, ub, sb) &&
                        printedElts < MAX_ARRAY_PRINT_LEN);

                if (arr.Length > MAX_ARRAY_PRINT_LEN)
                {
                    sb.Append(" ... ");
                    for (int ix = 0; ix < arr.Rank; ++ix)
                        sb.Append(')');
                    sb.Append(">");
                }

                return sb.ToString();
            }

			if (x is Cons) 
			{
				Cons cons = (Cons) x;
				StringBuilder stringBuilder = new StringBuilder();

                if (cons.Car() == Symbol.QUOTE)
                {
                    stringBuilder.Append("'");
                    stringBuilder.Append(WriteToString(cons.Cadr()));
                }

                else if (cons.Car() == Symbol.BACKQUOTE)
                {
                    stringBuilder.Append("`");
                    stringBuilder.Append(WriteToString(cons.Cadr()));
                }

                else if (cons.Car() == Symbol.UNQUOTE)
                {
                    stringBuilder.Append(",");
                    stringBuilder.Append(WriteToString(cons.Cadr()));
                }

                else if (cons.Car() == Symbol.SPLICE)
                {
                    stringBuilder.Append(",@");
                    stringBuilder.Append(WriteToString(cons.Cadr()));
                }
                else
                {
                    if (encloseList)
                        stringBuilder.Append("(");
                    stringBuilder.Append(WriteToString(cons.Car()));

                    Object o;
                    o = cons.Cdr();
                    int consLen = 0;
                    while (o != null && ++consLen < MAX_CONS_PRINT_LEN)
                    {
                        if (o.GetType() == typeof(Cons))
                        {
                            cons = (Cons)o;
                            stringBuilder.Append(" ");
                            stringBuilder.Append(WriteToString(cons.Car()));
                            o = cons.Cdr();
                        }
                        else
                        {
                            stringBuilder.Append(" . ");
                            stringBuilder.Append(WriteToString(o));
                            o = null;
                        }
                    }

                    if (o != null)
                        stringBuilder.Append(" ... ");

                    if (encloseList)
                        stringBuilder.Append(")");
                }

                return stringBuilder.ToString();
			}

            if (Type.GetTypeCode(x.GetType()) == TypeCode.Object)
                return string.Format(CultureInfo.CurrentCulture,
                                        "#<{0}>", x.ToString().Trim());

            return x.ToString().Trim();
		}


        internal static bool IncrementIndex (int ix, int[] ind, int[] lb, int[] ub, StringBuilder sb)
        {
            bool retval = false;
            if (ix >= 0)
            {
                if (ind[ix] < ub[ix])
                {
                    ++ind[ix];
                    sb.Append(" ");
                    retval = true;
                }
                else
                {
                    sb.Append(")");
                    ind[ix] = lb[ix];
                    retval = IncrementIndex(ix - 1, ind, lb, ub, sb);
                    if (retval)
                        sb.Append("(");
                }
            }

            return retval;
        }
	}
}
